package rolesystem.middle.managers;

import java.util.ArrayList;
import java.awt.*;

import javax.swing.*;

import rolesystem.middle.GUI.EnvVisualizer;
import rolesystem.middle.GUI.GuiControls;
import rolesystem.middle.GUI.StatePanel;
import rolesystem.middle.parameters.DefaultParameters;
import rolesystem.middle.utilityclasses.AgentData;
import rolesystem.middle.utilityclasses.AgentMap;
import rolesystem.middle.utilityclasses.PositionSq;
import rolesystem.middle.utilityclasses.SearchResult;
import rolesystem.middle.utilityclasses.SearchResultSquare;
import rolesystem.middle.utilityclasses.Square;

public class EnvironmentManager 
{
	/**
	 * X Size of the environment.
	 */
	static public int ENV_MAX_X;
	
	/**
	 * Y Size of the environment.
	 */
	static public int ENV_MAX_Y;
	
	public static int[] weightNumbers;
	
	static public int OBSTACLES_NUMBER;
	
	/**
	 * The environment, a matrix of Square.
	 */
	private static ArrayList<Square>[][] env;
	
	public static EnvVisualizer canvas;
	public static GuiControls envControls;
	public static StatePanel stateVisualization;
	private static JFrame frame;
	
	
	static	
	{
		ENV_MAX_X= DefaultParameters.ENV_MAX_X_DEFAULT;
		ENV_MAX_Y= DefaultParameters.ENV_MAX_Y_DEFAULT;
		OBSTACLES_NUMBER=DefaultParameters.OBSTACLES_NUMBER_DEFAULT;
		weightNumbers=DefaultParameters.WEIGHT_NUMBERS_DEFAULT;
		
		env= new ArrayList[ENV_MAX_X][ENV_MAX_Y];
		
		// Initialize the Square(s)
		reset();
		
		// Initialize the Visualization
		initVisualization();
		
		DataOutputManager.printString("## Environment Initialized ##"); 
	}
	
	public static void initialize(final int env_x,final int env_y, final int[] weightNumbers,final int obstacles)
	{
		EnvironmentManager.weightNumbers=weightNumbers;
		EnvironmentManager.OBSTACLES_NUMBER=obstacles;
		EnvironmentManager.ENV_MAX_X=env_x;
		EnvironmentManager.ENV_MAX_Y=env_y;	
	}
	
	public static void initVisualization()
	{
		frame = new JFrame();
		frame.setSize(DefaultParameters.FRAME_SIZE_X, DefaultParameters.FRAME_SIZE_Y);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		
		canvas = new EnvVisualizer(20,ENV_MAX_X,ENV_MAX_Y);
		canvas.setSize(DefaultParameters.ENVIRONMENT_PANEL_SIZE_X, DefaultParameters.ENVIRONMENT_PANEL_SIZE_Y);
		canvas.setLocation(DefaultParameters.ENVIRONMENT_PANEL_POSITION_X, DefaultParameters.ENVIRONMENT_PANEL_POSITION_Y);
		
		envControls = new GuiControls();
		envControls.setSize(DefaultParameters.CONTROL_PANEL_SIZE_X, DefaultParameters.CONTROL_PANEL_SIZE_Y);
		envControls.setLocation(DefaultParameters.CONTROL_PANEL_POSITION_X, DefaultParameters.CONTROL_PANEL_POSITION_Y);
		
		stateVisualization= new StatePanel();		
		stateVisualization.setSize(DefaultParameters.STATE_PANEL_SIZE_X, DefaultParameters.STATE_PANEL_SIZE_Y);
		stateVisualization.setLocation(DefaultParameters.STATE_PANEL_POSITION_X, DefaultParameters.STATE_PANEL_POSITION_Y);
		
		frame.setLayout(null);
		
		frame.getContentPane().add(canvas);
		frame.getContentPane().add(envControls);
		frame.getContentPane().add(stateVisualization);
		frame.setVisible(true);
		
	}
	
	public synchronized static void resetText()
	{
		stateVisualization.reset(); // Problema: interferisce con visualizzazione
	}
	
	
	public static void reset()
	{
		// Initialize the Square(s)
		for(int i=0;i< ENV_MAX_X; i++)
		{
			for(int j=0;j< ENV_MAX_Y; j++)
			{
				env[i][j]=new ArrayList<Square>();
				env[i][j].add(new Square());
				
				if(i==0 || j==0 || i== (ENV_MAX_X-1) || j==(ENV_MAX_Y-1) ) // FIXME = per tutti e due i progetti
					env[i][j].get(0).setBlocked(true); // FIXME funziona??
				else
					env[i][j].get(0).setBlocked(false);
			}    
		}  
		
		// Create obstacles
		for(int i=0;i< OBSTACLES_NUMBER; i++)
		{
			PositionSq randomPosition=new PositionSq((int) (Math.random() * (ENV_MAX_X-3))+1,(int) (Math.random() * (ENV_MAX_Y-3))+1);
			
			if(getObstacle(randomPosition)==false)
				setObstacle(randomPosition,true);
			else
				i--;
		} 
		
		// Create the Objects (non null weights)
		for(int i=0;i<weightNumbers.length;i++)
			generateObjects(weightNumbers[i], i+1);
	}
	
	private static void generateObjects(final int numberOfObjects, final int weight)
	{		
		for(int i=0;i< numberOfObjects; i++)
		{
			PositionSq randomPosition=new PositionSq((int) (Math.random() * (ENV_MAX_X-1)),(int) (Math.random() * (ENV_MAX_Y-1)));
			
			if(getObstacle(randomPosition)==false && getObjectWeight(randomPosition)==0)
				setObjectWeight(randomPosition,weight);
			else
				i--;		 	 
		} 
	}
	
	public static ArrayList<SearchResult> SearchRange(final int x, final int y, final int range, final boolean reserved_check)
	{
		ArrayList<SearchResult> search_result= new ArrayList<SearchResult>();
		
		PositionSq min= new PositionSq(x-range,y-range);
		PositionSq max= new PositionSq(x+range,y+range);
		
		min.checkAndCorrectBounds();
		max.checkAndCorrectBounds();
		
		for(int i=min.x;i<= max.x; i++)
		{
			for(int j=min.y;j<= max.y; j++)
			{
				PositionSq index_position= new PositionSq(i,j);
				int object_weight=getObjectWeight(index_position);
				
				if(object_weight!=0 && (getReserved(index_position)==false || reserved_check))
				{   
					search_result.add(new SearchResult(i,j,object_weight));
					System.out.println("Search - Match Found - x:" + i + " y:"+ j + " Peso:"+ object_weight); 
				}
			}    
		}
		
		return search_result;
	}
	
	public static ArrayList<SearchResult> searchObjectInRadius(final PositionSq position, final int max_range, final boolean reserved_check)
	{
		ArrayList<SearchResult> search_result;
		
		int range=0;
		
		do  // Loop until the search concludes.	
		{
			search_result=EnvironmentManager.SearchRange(position.x, position.y, range, reserved_check);
			
			if (search_result.isEmpty())
				range++;
			else
				break;	
		}while(range<=max_range);
		
		return search_result;
	}
	
	public static ArrayList<SearchResultSquare> SearchRangeForSquare(final int x, final int y, final int range,
			final int square_weight,final boolean weight_active,final boolean obstacle_active,
			final boolean reserved_active,final boolean known_active,final boolean knownP_active)
			{
		ArrayList<SearchResultSquare> search_result= new ArrayList<SearchResultSquare>();
		
		PositionSq min= new PositionSq(x-range,y-range);
		PositionSq max= new PositionSq(x+range,y+range);
		
		min.checkAndCorrectBounds();
		max.checkAndCorrectBounds();
		
		for(int i=min.x;i<= max.x; i++)
		{
			for(int j=min.y;j<= max.y; j++)
			{
				PositionSq index_position= new PositionSq(i,j);
				
				boolean check = ((!weight_active) || (getObjectWeight(index_position)==square_weight)) &&
						((!obstacle_active) || getObstacle(index_position)) &&
						((!reserved_active) || getReserved(index_position)) &&
						((!known_active) || getKnown(index_position)) &&
						((!knownP_active) || getKnownPheromone(index_position));
				
				if(check) 
					search_result.add(new SearchResultSquare(index_position,env[index_position.x][index_position.y].get(0)));
			}    
		}
		
		return search_result;
			}
	
	public synchronized int searchRangeForPheromoneNumber(PositionSq position, int range)
	{
		int squareNotExplored=0; 
				  
		// Check for possible position choices
		for(int i=-range;i<=range;i++)
		{
			for(int j=-range;j<=range;j++)
			{
				if(i==0 && j==0)
				  continue;
						  
				PositionSq candidate_pos=PositionSq.add(position, new PositionSq(i,j));
				if(candidate_pos.checkBounds())
				  continue;

				 if(getKnownPheromone(candidate_pos)==false)
					 squareNotExplored++;
			}  
		}  
				  		  
		return squareNotExplored; 
	}
	
	
	
	public static Square pickObject(PositionSq object_position)
	{	  
		if(object_position.checkBounds())	
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return null;
		}
		
		Square pickedObject=env[object_position.x][object_position.y].get(0);
		
		if(pickedObject.getWeight()==0)
			return null;
		
		setObjectWeight(object_position,0);
		setReserved(object_position,false);
		
		updateVisualization();
		
		return pickedObject;
	}
	
	public static PositionSq getAlternativeRoute(PositionSq pos, PositionSq dir)
	{
		return getAlternativeRoute(pos.getX(),pos.getY(),dir.getX(),dir.getY());
	}
	
	public static PositionSq getAlternativeRoute(int position_x,int position_y,int dir_x,int dir_y)
	{
		ArrayList<PositionSq> possible_route= new ArrayList<PositionSq>();
		ArrayList<Integer> distanceFromTarget= new ArrayList<Integer>();
		
		// Check for possible position choices
		for(int i=-1;i<=1;i++)
		{
			for(int j=-1;j<=1;j++)
			{
				if(i==0 && j==0)
					continue;
				
				PositionSq candidate_pos=new PositionSq(position_x+i, position_y+j);
				if(candidate_pos.checkBounds())
					continue;
				
				if(candidate_pos.x==(position_x+dir_x) && candidate_pos.y==(position_y+dir_y))
					continue;
				
				if(getObstacle(candidate_pos)==false)
				{  
					possible_route.add(candidate_pos); 
					distanceFromTarget.add(new Integer(Math.abs(dir_x-i) + Math.abs(dir_y-j) )); 
				}
			}  
		}  
		
		// Calculate minimum distance from the "perfect" but blocked route
		int min_dist=100;
		ArrayList<Integer> indexArray= new ArrayList<Integer>();
		
		for(int i=0;i<possible_route.size();i++)
		{
			if(Math.abs(distanceFromTarget.get(i).intValue())<min_dist)
			{  
				indexArray.clear();
				indexArray.add(new Integer(i));
				min_dist=Math.abs(distanceFromTarget.get(i).intValue());
			}
			if(Math.abs(distanceFromTarget.get(i).intValue())==min_dist)
			{  
				indexArray.add(new Integer(i));  
				min_dist=Math.abs(distanceFromTarget.get(i).intValue());
			}
		}
		
		if(indexArray.size()>0)
		{
			int index=(int)(Math.random()* (indexArray.size()-1)); 
			return possible_route.get(indexArray.get(index).intValue()); 
		}
		else
			return new PositionSq(position_x,position_y); 
	}
	
	public static void updateAgentPosition(int agent_id, PositionSq updatedPosition)
	{
		EnvVisualizer.updateAgent(agent_id,updatedPosition);
	}
	
	public static void stopSignal()
	{
		envControls.startStopButtonClicked();
	}
	
	public static void updateVisualization()
	{
		Graphics g = canvas.getGraphics();
		canvas.update(g);
	}
	
	public static boolean getObjectExist(final PositionSq position)
	{		
		if(getObjectWeight(position)==0)
			return false;
		else
			return true;
	}
	
	public static int getObjectWeight(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return 0;
		}
		
		return env[position.x][position.y].get(0).getWeight();
	}
	
	public static void setObjectWeight(final PositionSq position,final int weight)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return;
		}
		
		env[position.x][position.y].get(0).setWeight(weight);
	}
	
	public static boolean getObstacle(final int x,final int y)
	{
		final PositionSq position=new PositionSq(x,y);
		
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return env[x][y].get(0).getBlocked();  
	}
	
	public static boolean getObstacle(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return env[position.x][position.y].get(0).getBlocked();  
	}
	
	public static void setObstacle(final PositionSq position, final boolean blocked)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return;
		}
		
		env[position.x][position.y].get(0).setBlocked(blocked);  
	} 
	
	public synchronized static boolean getAgentHere(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return AgentMap.isAgentHere(position);
	}
	
	public static boolean getObstacleAndAgents(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return env[position.x][position.y].get(0).getBlocked() || AgentMap.isAgentHere(position);
	}
	
	public static boolean getReserved(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return env[position.x][position.y].get(0).getReserved();  
	}
	
	public static void setReserved(final PositionSq position, final boolean reserved)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return;
		}
		
		env[position.x][position.y].get(0).setReserved(reserved);  
	}
	
	public static boolean getReservedAndSetIfFalse(final PositionSq position)
	{
		/*if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}*/
		
		return env[position.x][position.y].get(0).getReservedAndSetIfFalse();  
	}
	
	public static boolean getKnown(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return env[position.x][position.y].get(0).getKnown();  
	}
	
	public static void setKnown(final PositionSq position, final boolean known)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return;
		}
		
		env[position.x][position.y].get(0).setKnown(known);  
	}
	
	public static boolean getKnownPheromone(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return false;
		}
		
		return env[position.x][position.y].get(0).getKnownPheromone();  
	}
	
	public static void setKnownPheromone(final PositionSq position, final boolean knownPheromone)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return;
		}
		
		env[position.x][position.y].get(0).setKnownPheromone(knownPheromone);  
	}
	
	public static Class getOtherData(final PositionSq position)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return null;
		}
		
		return env[position.x][position.y].get(0).getOtherData();  
	}
	
	public static void setOtherData(final PositionSq position,final Class otherData)
	{
		if(position.checkBounds())
		{
			DataOutputManager.printString("ERROR: position out of bounds");
			return;
		}
		
		env[position.x][position.y].get(0).setOtherData(otherData);  
	}
	
}

